package weeny.pre.face.operation;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.swing.JComboBox;

import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.expression.Predicate;
import prefuse.data.parser.DataParser;

import weeny.util.logs.Debug;

/**
 * This class is used by {@link OperationBox} only. For more details, please
 * see {@link OperationBox}.
 * 
 * Basically, to use this operation, you should inherit this class, and then
 * you give a {@link #title}, a {@link #itemList}. The title and items will be
 * listed in the operation combo box. And you should define the item action by 
 * implementing {@link #itemInvoked(String, boolean)}, in which funcion, you may
 * need to call {@link OperationBox#updateDetail(java.awt.Component)} function
 * to update the control UI, such as text field, or combobox, for accepting user
 * input. You can see {@link GroupOperation} to get a better idea. After you
 * make your operation, you can add the operation to the operationBox by calling
 * {@link OperationBox#addOperation(AbstractOperation)}.
 * 
 * @author weeny
 */
public abstract class AbstractOperation{
	/**
	 * Every operation has a group of items, which are listed in the operation
	 * combo, every one of them should follow this format. But you should not
	 * call this by yourself. You only need to set the {@link #itemList}, then
	 * {@link ComboItem} will be created by {@link #prepareCombo(JComboBox)}.
	 * 
	 * @author weeny
	 */
	public static class ComboItem{
		public final String title;
		public final String item;
		public ComboItem(String title, String item) {
			this.title = title;
			this.item = item;
		}
	}
	protected static final String SEPARATOR = "(-----------)";
	/**
	 * The title will be shown in the operation combo box to indicate that the
	 * following items belong to this title (i.e., group)
	 */
	protected String title;
	/**
	 * The items will be shown after the title, and every item will be indented
	 */
	protected List<String> itemList = new ArrayList<String>();
	/**
	 * You may need to call functions in the {@link OperationBox}. Operaton and
	 * OperationBox are combined together. You 
	 */
	protected OperationBox operationBox = null;
	
	/**
	 * Constructor, we need to specify the operationBox this operation belonging
	 * to. Also, we will check if the operation and the operationbox match when
	 * calling {@link OperationBox#addOperation(AbstractOperation)}
	 */
	public AbstractOperation(OperationBox operationBox) {
		this.operationBox = operationBox;
	}
	
	/**
	 * This function is called by {@link OperationBox}. You should not worry
	 * about it, and should not change it.
	 */
	public void prepareCombo(JComboBox box){
		box.addItem(new ComboItem(title, title));
		for (String item : itemList){
			box.addItem(new ComboItem(title, item));
		}
	}
	
	public OperationBox getOperationBox(){
		return operationBox;
	}

	public String getTitle(){
		return title;
	}
	
	/**
	 * Collect all tables that contain tuples meeting the Predicate. 
	 */
	protected Set<Table> itemTables(Predicate filter){
		Set<Table> result = new HashSet<Table>();
		Iterator<Tuple> it = operationBox.getVisualization().items(filter);
		while (it.hasNext()){
			Table table = it.next().getTable();
			if (table == null) {
				throw new IllegalArgumentException("Has no table");
			}
			result.add(table);
		}
		return result;
	}
	
	/**
	 * Get the parser for the column in every filtered tuple. We first collect
	 * all tables that contain tuples meeting the filter. And check if the
	 * columns in them have the same data parser. If they do, return the parser,
	 * otherwise, return null.
	 */
	protected DataParser dataParser(Predicate filter, String columnName){
		Iterator<Table> it = itemTables(filter).iterator();
		DataParser result = null;
		while (it.hasNext()) {
			Table table = it.next();
			if (table.getColumn(columnName) == null){
				return null;
			}
			DataParser parser = table.getColumn(columnName).getParser();
			if (result == null) {
				result = parser;
			}
			else if (result.equals(parser)) {
				continue;
			}
			else if (result.getClass().equals(parser.getClass())) {
				Debug.prob("Same class ("+result.getClass()+"), Same parser??");
				continue;
			}
			else {
				Debug.prob("Different parser: " +
						""+result.getClass()+" v.s. "+parser.getClass());
				return null;
			}
		}
		return result;
	}
	/**
	 * Get the type for the column in every filtered tuple. We first collect all
	 * tables that contain tuples meeting the filter. And check if the columns
	 * in them have the same type. If they do, return type, otherwise, return
	 * null.
	 */
	protected Class columnType(Predicate filter, String columnName){
		Iterator<Table> it = itemTables(filter).iterator();
		Class columnType = null;
		while (it.hasNext()){
			Class tempType = it.next().getColumnType(columnName); 
			if ( tempType == null) {
				return null;
			}
			else if (columnType != null && tempType != columnType) {
				return null;
			}
			columnType = tempType;
		}
		return columnType;
	}
	/**
	 * This is a callback function, which is invoked when the user click one of
	 * the item listed in the operation combo box. More details can be found in
	 * 
	 */
	public abstract void itemInvoked(String item, boolean isAltDown);
}